// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/exchange/factory/symbology/product.h"

#include <map>
#include <memory>
#include <regex>
#include <set>
#include <string>
#include <utility>

#include <fmt/format.h>

#include "coin2/exchange/base/api_util/raw_subscriber_util.h"
#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/symbology/product_encyclopedia.h"
#include "coin2/exchange/factory/feed/symbol_updater.h"
#include "coin2/flow/symbol_group.h"

// products

#include "coin2/exchange/apollox/symbology/product.h"
#include "coin2/exchange/apollox_futures/symbology/product.h"
#include "coin2/exchange/binance/symbology/product.h"
#include "coin2/exchange/binance_futures/symbology/product.h"
#include "coin2/exchange/binanceus/symbology/product.h"
#include "coin2/exchange/bit/symbology/product.h"
#include "coin2/exchange/bitbank/symbology/product.h"
#include "coin2/exchange/bitflyer_futures/symbology/product.h"
#include "coin2/exchange/bitget/symbology/product.h"
#include "coin2/exchange/bitget_futures/symbology/product.h"
#include "coin2/exchange/bithumb/symbology/product.h"
#include "coin2/exchange/bitmart/symbology/product.h"
#include "coin2/exchange/bitmex/symbology/product.h"
#include "coin2/exchange/bitstamp/symbology/product.h"
#include "coin2/exchange/bittrex/symbology/product.h"
#include "coin2/exchange/btcturk/symbology/product.h"
#include "coin2/exchange/bybit/symbology/product.h"
#include "coin2/exchange/bybit_futures/symbology/product.h"
#include "coin2/exchange/coincheck/symbology/product.h"
#include "coin2/exchange/coingecko/symbology/product.h"
#include "coin2/exchange/coinone/symbology/product.h"
#include "coin2/exchange/crypto/symbology/product.h"
#include "coin2/exchange/crypto_futures/symbology/product.h"
#include "coin2/exchange/deribit_futures/symbology/product.h"
#include "coin2/exchange/deribit_index/symbology/product.h"
#include "coin2/exchange/deribit_options/symbology/product.h"
#include "coin2/exchange/dydx_futures/symbology/product.h"
#include "coin2/exchange/ftx/symbology/product.h"
#include "coin2/exchange/ftx_futures/symbology/product.h"
#include "coin2/exchange/gateio/symbology/product.h"
#include "coin2/exchange/gateio_futures/symbology/product.h"
#include "coin2/exchange/gdax/symbology/product.h"
#include "coin2/exchange/gopax/symbology/product.h"
#include "coin2/exchange/huobi/symbology/product.h"
#include "coin2/exchange/huobi_futures/symbology/product.h"
#include "coin2/exchange/korbit/symbology/product.h"
#include "coin2/exchange/kraken/symbology/product.h"
#include "coin2/exchange/kucoin/symbology/product.h"
#include "coin2/exchange/kucoin_futures/symbology/product.h"
#include "coin2/exchange/lbank/symbology/product.h"
#include "coin2/exchange/lmax_digital/symbology/product.h"
#include "coin2/exchange/maicoin/symbology/product.h"
#include "coin2/exchange/mercado/symbology/product.h"
#include "coin2/exchange/mexc/symbology_v3/product.h"
#include "coin2/exchange/mexc_futures/symbology/product.h"
#include "coin2/exchange/okex/symbology/product.h"
#include "coin2/exchange/okex_futures/symbology/product.h"
#include "coin2/exchange/otc_options/symbology/product.h"
#include "coin2/exchange/pancakeswap/symbology/product.h"
#include "coin2/exchange/phemex/symbology/product.h"
#include "coin2/exchange/phemex_futures/symbology/product.h"
#include "coin2/exchange/prex/symbology/product.h"
#include "coin2/exchange/prex_futures/symbology/product.h"
#include "coin2/exchange/quoinex/symbology/product.h"
#include "coin2/exchange/uniswap/symbology/product.h"
#include "coin2/exchange/upbit/symbology/product.h"
#include "coin2/exchange/upbit_id/symbology/product.h"
#include "coin2/exchange/upbit_sg/symbology/product.h"
#include "coin2/exchange/upbit_th/symbology/product.h"

namespace impl {

using namespace coin::proto;
using coin2::exchange::apollox::symbology::ApolloxProduct;
using coin2::exchange::apollox_futures::symbology::ApolloxFuturesProduct;
using coin2::exchange::base::market::ExchangeTypeToString;
using coin2::exchange::base::market::MarketExchangeApi;
using coin2::exchange::base::market::MarketTypeToString;
using coin2::exchange::base::symbology::ProductEncyclopedia;
using coin2::exchange::binance::symbology::BinanceProduct;
using coin2::exchange::binance_futures::symbology::BinanceFuturesProduct;
using coin2::exchange::binance_us::symbology::BinanceUsProduct;
using coin2::exchange::bit::symbology::BitProduct;
using coin2::exchange::bitbank::symbology::BitbankProduct;
using coin2::exchange::bitflyer_futures::symbology::BitflyerFuturesProduct;
using coin2::exchange::bitget::symbology::BitgetProduct;
using coin2::exchange::bitget_futures::symbology::BitgetFuturesProduct;
using coin2::exchange::bithumb::symbology::BithumbProduct;
using coin2::exchange::bitmart::symbology::BitmartProduct;
using coin2::exchange::bitmex_futures::symbology::BitmexFuturesProduct;
using coin2::exchange::bitstamp::symbology::BitstampProduct;
using coin2::exchange::bittrex::symbology::BittrexProduct;
using coin2::exchange::btcturk::symbology::BtcturkProduct;
using coin2::exchange::bybit::symbology::BybitProduct;
using coin2::exchange::bybit_futures::symbology::BybitFuturesProduct;
using coin2::exchange::coincheck::symbology::CoincheckProduct;
using coin2::exchange::coingecko::symbology::CoingeckoProduct;
using coin2::exchange::coinone::symbology::CoinoneProduct;
using coin2::exchange::crypto::symbology::CryptoProduct;
using coin2::exchange::crypto_futures::symbology::CryptoFuturesProduct;
using coin2::exchange::deribit_futures::symbology::DeribitFuturesProduct;
using coin2::exchange::deribit_index::symbology::DeribitIndexProduct;
using coin2::exchange::deribit_options::symbology::DeribitOptionsProduct;
using coin2::exchange::dydx_futures::symbology::DydxFuturesProduct;
using coin2::exchange::factory::feed::NewSymbolUpdater;
using coin2::exchange::ftx::symbology::FtxProduct;
using coin2::exchange::ftx_futures::symbology::FtxFuturesProduct;
using coin2::exchange::gateio::symbology::GateioProduct;
using coin2::exchange::gateio_futures::symbology::GateioFuturesProduct;
using coin2::exchange::gdax::symbology::GdaxProduct;
using coin2::exchange::gopax::symbology::GopaxProduct;
using coin2::exchange::huobi::symbology::HuobiProduct;
using coin2::exchange::huobi_futures::symbology::HuobiFuturesProduct;
using coin2::exchange::korbit::symbology::KorbitProduct;
using coin2::exchange::kraken::symbology::KrakenProduct;
using coin2::exchange::kucoin::symbology::KucoinProduct;
using coin2::exchange::kucoin_futures::symbology::KucoinFuturesProduct;
using coin2::exchange::lbank::symbology::LbankProduct;
using coin2::exchange::lmax_digital::symbology::LmaxdigitalProduct;
using coin2::exchange::maicoin::symbology::MaicoinProduct;
using coin2::exchange::mercado::symbology::MercadoProduct;
using coin2::exchange::mexc::symbology_v3::MexcProduct;
using coin2::exchange::mexc_futures::symbology::MexcFuturesProduct;
using coin2::exchange::okex::symbology::OkexProduct;
using coin2::exchange::okex_futures::symbology::OkexFuturesProduct;
using coin2::exchange::otc_options::symbology::OtcOptionsProduct;
using coin2::exchange::pancakeswap::symbology::PancakeswapProduct;
using coin2::exchange::phemex::symbology::PhemexProduct;
using coin2::exchange::phemex_futures::symbology::PhemexFuturesProduct;
using coin2::exchange::prex::symbology::PrexProduct;
using coin2::exchange::prex_futures::symbology::PrexFuturesProduct;
using coin2::exchange::quoinex::symbology::QuoinexProduct;
using coin2::exchange::uniswap::symbology::UniswapProduct;
using coin2::exchange::upbit::symbology::UpbitProduct;
using coin2::exchange::upbit_id::symbology::UpbitIdProduct;
using coin2::exchange::upbit_sg::symbology::UpbitSgProduct;
using coin2::exchange::upbit_th::symbology::UpbitThProduct;
using coin2::flow::SymbolGroupConfig;

std::unique_ptr<IProduct> CreateProductFromNativeString(
    const MarketExchangeApi& mea,
    const std::string& symbol,
    int64_t resolve_timestamp,
    bool die_on_failure) {
  auto product = CreateProductFromNativeStringNoThrow(mea, symbol, resolve_timestamp);
  if (die_on_failure) {
    CHECK(product) << "[Product Factory] unknown native symbol: "
                   << fmt::format(
                          "{}-{}-{}",
                          MarketTypeToString(mea.market),
                          ExchangeTypeToString(mea.exchange),
                          symbol);
  }
  return product;
}

std::unique_ptr<IProduct> CreateProductFromNativeStringNoThrow(
    const MarketExchangeApi& mea,
    const std::string& symbol,
    int64_t resolve_timestamp) {
  // TODO(jshin): use symbology server
  std::unique_ptr<IProduct> product;
  if (mea == MarketExchangeApi{Spot, Huobi, "v1"}) {
    product = HuobiProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Huobi, "v1"} ||
      mea == MarketExchangeApi{Futures, Huobi, "v1-swap"} ||
      mea == MarketExchangeApi{Futures, Huobi, "v1-linear-swap"}) {
    product = HuobiFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Bitmex, "v1"}) {
    product = BitmexFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Spot, Okex, "v3"} || mea == MarketExchangeApi{Spot, Okex, "v5"}) {
    product = OkexProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Okex, "v3"} ||
      mea == MarketExchangeApi{Futures, Okex, "v3-swap"} ||
      mea == MarketExchangeApi{Futures, Okex, "v5"} ||
      mea == MarketExchangeApi{Futures, Okex, "v5-swap"}) {
    product = OkexFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Phemex, "v1"} ||
      mea == MarketExchangeApi{Futures, Phemex, "v1-linear"}) {
    product = PhemexFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Binance, "v1"} ||
      mea == MarketExchangeApi{Futures, Binance, "v1-delivery"}) {
    product = BinanceFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Binance, "v1"}) {
    product = BinanceProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Binanceus, "v1"}) {
    product = BinanceUsProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Kraken, "v1"}) {
    product = KrakenProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Korbit, "v1"}) {
    product = KorbitProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bitbank, "v1"}) {
    product = BitbankProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Upbit, "v1"}) {
    product = UpbitProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Upbitsg, "v1"}) {
    product = UpbitSgProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Upbitid, "v1"}) {
    product = UpbitIdProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Upbitth, "v1"}) {
    product = UpbitThProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Coinone, "v2"}) {
    product = CoinoneProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Gdax, "v1"}) {
    product = GdaxProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Gopax, "v1"}) {
    product = GopaxProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Quoinex, "v2"}) {
    product = QuoinexProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bithumb, "v2"}) {
    product = BithumbProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Bitflyer, "v1"}) {
    product = BitflyerFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bitflyer, "v1"}) {
    product = BitflyerFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Spot, Bybit, "v1"} || mea == MarketExchangeApi{Spot, Bybit, "v3"} ||
      mea == MarketExchangeApi{Spot, Bybit, "v5"}) {
    product = BybitProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Bybit, "v2"} ||
      mea == MarketExchangeApi{Futures, Bybit, "v3"} ||
      mea == MarketExchangeApi{Futures, Bybit, "v2-linear"} ||
      mea == MarketExchangeApi{Futures, Bybit, "v3-linear"} ||
      mea == MarketExchangeApi{Futures, Bybit, "v3-linear-usdc"} ||
      mea == MarketExchangeApi{Futures, Bybit, "v5"} ||
      mea == MarketExchangeApi{Futures, Bybit, "v5-linear"}) {
    product = BybitFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Lbank, "v2"}) {
    product = LbankProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bitmart, "v3"}) {
    product = BitmartProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Maicoin, "v2"}) {
    product = MaicoinProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bittrex, "v3"}) {
    product = BittrexProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Ftx, "v1"}) {
    product = FtxFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Ftx, "v1"}) {
    product = FtxProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bitstamp, "v2"}) {
    product = BitstampProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Prex, "v1"}) {
    product = PrexProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Prex, "v1"}) {
    product = PrexFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Lmaxdigital, "v1"}) {
    product = LmaxdigitalProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Deribit, "v2"}) {
    product = DeribitFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Options, Deribit, "v2"}) {
    product = DeribitOptionsProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Index, Deribit, "v2"}) {
    product = DeribitIndexProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Coincheck, "v1"}) {
    product = CoincheckProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Spot, Uniswap, "v3"} ||
      mea == MarketExchangeApi{Spot, Uniswap, "v3-arbitrum"} ||
      mea == MarketExchangeApi{Spot, Uniswap, "v2"}) {
    product = UniswapProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Gateio, "v4"}) {
    product = GateioProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Gateio, "v4"} ||
      mea == MarketExchangeApi{Futures, Gateio, "v4-btc"} ||
      mea == MarketExchangeApi{Futures, Gateio, "v4-usdt"} ||
      mea == MarketExchangeApi{Futures, Gateio, "v4-delivery-btc"} ||
      mea == MarketExchangeApi{Futures, Gateio, "v4-delivery"}) {
    product = GateioFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Gateio, "v4"}) {
    product = GateioProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Futures, Gateio, "v4"} ||
      mea == MarketExchangeApi{Futures, Gateio, "v4-delivery"}) {
    product = GateioFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Phemex, "v1"}) {
    product = PhemexProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Kucoin, "v1"}) {
    product = KucoinFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Kucoin, "v1"}) {
    product = KucoinProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Dydx, "v3"}) {
    product = DydxFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Pancakeswap, "v2"}) {
    product = PancakeswapProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Btcturk, "v1"}) {
    product = BtcturkProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (
      mea == MarketExchangeApi{Spot, Crypto, "v2"} ||
      mea == MarketExchangeApi{Spot, Crypto, "v1"}) {
    product = CryptoProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Crypto, "v1"}) {
    product = CryptoFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Mexc, "v1"}) {
    product = MexcFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Mexc, "v3"}) {
    product = MexcProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Coingecko, "v3"}) {
    product = CoingeckoProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bitget, "v1"}) {
    product = BitgetProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Bitget, "v1"}) {
    product = BitgetFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Lbank, "v2"}) {
    product = LbankProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Futures, Apollox, "v1"}) {
    product = ApolloxFuturesProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Apollox, "v1"}) {
    product = ApolloxProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Options, Otc, "v1"}) {
    product = OtcOptionsProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Mercado, "v4"}) {
    product = MercadoProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  } else if (mea == MarketExchangeApi{Spot, Bit, "v1"}) {
    product = BitProduct::FromNativeStrNoThrow(symbol, resolve_timestamp);
  }
  return product;
}

#define MATCH(M, E, CLS)                                       \
  if (market == (M) && exchange == (E)) {                      \
    return std::make_unique<CLS>(CLS::FromNormStr(symbol, 0)); \
  }

std::unique_ptr<IProduct> CreateProductFromNormString(
    MarketType market,
    ExchangeType exchange,
    const std::string& symbol,
    int64_t resolve_timestamp,
    bool die_on_failure) {
  auto product = CreateProductFromNormStringNoThrow(market, exchange, symbol, resolve_timestamp);
  if (die_on_failure) {
    CHECK(product) << "[Product Factory] unknown norm symbol: "
                   << fmt::format(
                          "{}-{}-{}",
                          MarketTypeToString(market),
                          ExchangeTypeToString(exchange),
                          symbol);
  }
  return product;
}

std::unique_ptr<IProduct> CreateProductFromNormString(
    // const MarketExchangeApi& mea,
    const MarketExchangeApi& mea,
    const std::string& symbol,
    int64_t resolve_timestamp,
    bool die_on_failure) {
  return CreateProductFromNormString(
      mea.market,
      mea.exchange,
      symbol,
      resolve_timestamp,
      die_on_failure);
}

// for norm symbols, we don't need to know api_version
std::unique_ptr<IProduct> CreateProductFromNormStringNoThrow(
    // const MarketExchangeApi& mea,
    MarketType market,
    ExchangeType exchange,
    const std::string& symbol,
    int64_t resolve_timestamp) {
  std::unique_ptr<IProduct> product;
  auto me = std::tie(market, exchange);
  if (me == std::forward_as_tuple(Spot, Huobi) || me == std::forward_as_tuple(Margin, Huobi)) {
    product = HuobiProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Huobi)) {
    product = HuobiFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Bitmex)) {
    product = BitmexFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (
      me == std::forward_as_tuple(Spot, Okex) || me == std::forward_as_tuple(ExWallet, Okex)) {
    product = OkexProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (
      me == std::forward_as_tuple(Futures, Okex) || me == std::forward_as_tuple(Futures, Okex)) {
    product = OkexFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Binance)) {
    product = BinanceFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (
      me == std::forward_as_tuple(Spot, Binance) || me == std::forward_as_tuple(Margin, Binance)) {
    product = BinanceProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Binanceus)) {
    product = BinanceUsProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Kraken)) {
    product = KrakenProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Korbit)) {
    product = KorbitProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bitbank)) {
    product = BitbankProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Upbit)) {
    product = UpbitProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Upbitsg)) {
    product = UpbitSgProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Upbitid)) {
    product = UpbitIdProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Upbitth)) {
    product = UpbitThProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Coinone)) {
    product = CoinoneProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Gdax)) {
    product = GdaxProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Gopax)) {
    product = GopaxProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Quoinex)) {
    product = QuoinexProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bithumb)) {
    product = BithumbProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Bitflyer)) {
    product = BitflyerFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bitflyer)) {
    product = BitflyerFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bybit)) {
    product = BybitProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Bybit)) {
    product = BybitFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Lbank)) {
    product = LbankProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bitmart)) {
    product = BitmartProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Maicoin)) {
    product = MaicoinProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bittrex)) {
    product = BittrexProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Ftx)) {
    product = FtxFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Ftx)) {
    product = FtxProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bitstamp)) {
    product = BitstampProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Prex)) {
    product = PrexProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Prex)) {
    product = PrexFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Lmaxdigital)) {
    product = LmaxdigitalProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Deribit)) {
    product = DeribitFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Options, Deribit)) {
    product = DeribitOptionsProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Index, Deribit)) {
    product = DeribitIndexProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Coincheck)) {
    product = CoincheckProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Uniswap)) {
    product = UniswapProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Gateio)) {
    product = GateioProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Gateio)) {
    product = GateioFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Phemex)) {
    product = PhemexFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Phemex)) {
    product = PhemexProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Kucoin)) {
    product = KucoinFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (
      me == std::forward_as_tuple(Spot, Kucoin) || me == std::forward_as_tuple(ExWallet, Kucoin)) {
    product = KucoinProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Dydx)) {
    product = DydxFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Pancakeswap)) {
    product = PancakeswapProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Btcturk)) {
    product = BtcturkProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Crypto)) {
    product = CryptoProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Crypto)) {
    product = CryptoFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Mexc)) {
    product = MexcFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Mexc)) {
    product = MexcProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Coingecko)) {
    product = CoingeckoProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bitget)) {
    product = BitgetProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Bitget)) {
    product = BitgetFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Lbank)) {
    product = LbankProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Futures, Apollox)) {
    product = ApolloxFuturesProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Apollox)) {
    product = ApolloxProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Options, Otc)) {
    product = OtcOptionsProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Mercado)) {
    product = MercadoProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  } else if (me == std::forward_as_tuple(Spot, Bit)) {
    product = BitProduct::FromNormStrNoThrow(symbol, resolve_timestamp);
  }
  return product;
}

// In fact, not unique.
// Futures:Huobi:BTC-USD.QUARTER
std::unique_ptr<IProduct> CreateProductFromUniqueString(
    const std::string& symbol,
    const int64_t resolve_timestamp) {
  std::vector<std::string> out;
  out = Split(symbol, ":");
  if (out.size() != 3) {
    throw std::runtime_error("CreateProductFromUniqueString fails: " + symbol);
  }

  auto market = coin2::exchange::base::market::StringToMarketType(out[0]);
  if (!market) {
    throw std::runtime_error("CreateProductFromUniqueString fails: " + symbol);
  }

  auto exchange = coin2::exchange::base::market::StringToExchangeType(out[1]);
  if (!exchange) {
    throw std::runtime_error("CreateProductFromUniqueString fails: " + symbol);
  }

  const auto& product_str = out[2];
  return CreateProductFromNormString(
      market.value(),
      exchange.value(),
      product_str,
      resolve_timestamp);
}

std::unique_ptr<IProduct>
ResolveProductRequest(const ProductRequest& req, int64_t resolve_timestamp, bool die_on_failure) {
  const auto mea = MarketExchangeApi::FromString(req.mea());
  if (req.has_native()) {
    return CreateProductFromNativeString(mea, req.native(), resolve_timestamp, die_on_failure);
  } else if (req.has_norm()) {
    return CreateProductFromNormString(mea, req.norm(), resolve_timestamp, die_on_failure);
  } else {
    THROW();
  }
  return {};
}

std::string RequestSymbolString(const ProductRequest& req) {
  return req.has_norm() ? req.norm() : req.native();
}

// for live subscription
std::vector<std::unique_ptr<IProduct>> FilterProductWithInSymbolGroups(
    const MarketExchangeApi& mea,
    std::vector<std::unique_ptr<IProduct>>& pre_products) {
  const auto& lass_products =
      coin2::flow::SymbolGroupConfig::GetInstance().GetAllRelativeNormSymbols(mea);
  std::vector<std::unique_ptr<IProduct>> out_products;
  for (auto& prod : pre_products) {
    if (mea == MarketExchangeApi{Options, Deribit, "v2"}) {
      // Deribit options case, symbol groups use .ALL to represent strike price.
      DeribitOptionsProduct* deribit_prod = dynamic_cast<DeribitOptionsProduct*>(prod.get());
      std::string norm_replace_price = prod->relative_norm();
      int64_t strike_price = deribit_prod->strike_price() * deribit_prod->strike_price_multiplier();
      std::string strike_price_str = std::to_string(strike_price);
      norm_replace_price = replace_string(norm_replace_price, strike_price_str, "ALL");
      if (std::find(lass_products.begin(), lass_products.end(), norm_replace_price) !=
          lass_products.end()) {
        out_products.push_back(std::move(prod));
      } else {
        LOG(INFO) << "Skip, NOT FOUND: " << prod->relative_norm();
      }
    } else if (
        std::find(lass_products.begin(), lass_products.end(), prod->relative_norm()) !=
        lass_products.end()) {
      out_products.push_back(std::move(prod));
    } else {
      LOG(INFO) << "Skip, NOT FOUND: " << prod->relative_norm();
    }
  }
  return out_products;
}

std::vector<std::unique_ptr<IProduct>> ProductRequestsFromConfig(
    const std::string& mea,
    const ProductConfig& config,
    int64_t timestamp,
    bool die_on_failure) {
  std::vector<ProductRequest> requests;
  bool realtime = config.resolve_realtime();
  const auto& symbol_groups = SymbolGroupConfig::GetInstance();
  if (config.query_and_update_symbols()) {
    std::set<std::string> natives;
    const auto& _mea = MarketExchangeApi::FromString(mea);
    auto symbol_updater = coin2::exchange::factory::feed::NewSymbolUpdater(_mea);
    for (const auto& norm : config.norms()) {
      auto _to_update = symbol_updater->GetUpdatedNormSymbols(norm);
      for (const auto& [_norm, _native] : _to_update) {
        if (natives.count(_native) == 0) {
          natives.insert(_native);
          ProductRequest req;
          req.set_mea(mea);
          req.set_norm(_norm);
          req.set_resolve_realtime(realtime);
          requests.push_back(req);
        }
      }
    }
    for (const auto& group : config.groups()) {
      CHECK_EQ_THROW(group.size(), 1) << "[Config] only a-z groups are supported: " << group;
      auto norms = symbol_groups.GetRelativeNormSymbols(_mea, group[0]);
      for (auto norm : norms) {
        auto _to_update = symbol_updater->GetUpdatedNormSymbols(norm);
        for (const auto& [_norm, _native] : _to_update) {
          if (natives.count(_native) == 0) {
            natives.insert(_native);
            ProductRequest req;
            req.set_mea(mea);
            req.set_norm(_norm);
            req.set_resolve_realtime(realtime);
            requests.push_back(req);
          }
        }
      }
    }
  } else {
    for (const auto& norm : config.norms()) {
      ProductRequest req;
      req.set_mea(mea);
      req.set_norm(norm);
      req.set_resolve_realtime(realtime);
      requests.push_back(req);
    }
    for (const auto& group : config.groups()) {
      CHECK_EQ_THROW(group.size(), 1) << "[Config] only a-z groups are supported: " << group;
      auto norms =
          symbol_groups.GetRelativeNormSymbols(MarketExchangeApi::FromString(mea), group[0]);
      for (auto norm : norms) {
        ProductRequest req;
        req.set_mea(mea);
        req.set_norm(norm);
        req.set_resolve_realtime(realtime);
        requests.push_back(req);
      }
    }
  }
  for (const auto& native : config.natives()) {
    ProductRequest req;
    req.set_mea(mea);
    req.set_native(native);
    req.set_resolve_realtime(realtime);
    requests.push_back(req);
  }
  if (config.has_norm_format()) {
    SymbolGroupConfig::ResetInstance();
    const auto& _mea = MarketExchangeApi::FromString(mea);
    const auto* pic = ProductEncyclopedia::GetProductHolderManager()
                          ->GetMutableProductInfoConstantsManager()
                          ->GetProductInfoConstant(_mea.market, _mea.exchange, timestamp);
    CHECK(pic);
    const auto& pib = pic->GetProductInfoBundle();
    for (const auto& pip : pib.product_infos()) {
      std::string norm_str = pip.symbol();
      std::smatch matches;
      if (std::regex_match(norm_str, matches, std::regex(config.norm_format()))) {
        ProductRequest req;
        req.set_mea(mea);
        if (mea.rfind("Options.Deribit", 0) == 0) {
          req.set_native(pip.native_symbol());
        } else {
          req.set_norm(norm_str);
        }
        req.set_resolve_realtime(realtime);
        requests.push_back(req);
      }
    }
  }
  if (mea.rfind("Spot.Coingecko", 0) == 0) {
    CoingeckoProduct::InitProductInfoConstant(timestamp);
  }
  // skip duplicated symbols
  std::map<std::string, std::unique_ptr<IProduct>> products;
  std::map<std::string, ProductRequest> unique_requests;
  for (auto& req : requests) {
    auto prod = ResolveProductRequest(req, timestamp, die_on_failure);
    if (!prod) {
      CHECK(!die_on_failure);
      continue;
    }
    auto native_symbol = prod->feed_native_symbol();
    if (products.count(native_symbol) > 0) {
      auto& req2 = unique_requests.at(native_symbol);
      LOG(INFO) << "[config] symbols duplicated: " << RequestSymbolString(req) << " vs "
                << RequestSymbolString(req2);
    } else {
      products[native_symbol] = std::move(prod);
      unique_requests[native_symbol] = req;
    }
  }

  std::vector<std::unique_ptr<IProduct>> out;
  for (auto& [native_symbol, prod] : products) {
    out.push_back(std::move(prod));
  }
  return out;
}

std::vector<std::unique_ptr<IProduct>> ResolveProductRequestFromPotentiallyWildcard(
    const ProductRequest& req,
    int64_t resolve_timestamp,
    bool die_on_failure) {
  coin2::exchange::symbology::ProductConfig pconf;
  auto relative_norm = req.norm();
  if (relative_norm.find("*") != std::string::npos) {
    pconf.set_norm_format(relative_norm);
  } else {
    pconf.add_norms(relative_norm);
  }
  return ProductRequestsFromConfig(req.mea(), pconf, resolve_timestamp, die_on_failure);
}

}  // namespace impl
